///\file

#include <server.h>
#include <rpc/rpc.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <mysql.h>
#include <string.h>
#include <chronometer.h>

#define TABLE    "Funcionario"

#define QUERY_TEMPLATE_SELECT    "SELECT * FROM "
#define QUERY_TEMPLATE_WHERE     " WHERE "
#define QUERY_TEMPLATE_AND       " AND "
#define QUERY_TEMPLATE_NOME      " nome = '"
#define QUERY_TEMPLATE_CPF       " CPF = '"
#define QUERY_TEMPLATE_RG        " RG = '"
#define QUERY_TEMPLATE_CARGO     " cargo = '"
#define QUERY_TEMPLATE_SEPARATOR "'"
#define QUERY_TEMPLATE_END       ";"

static MYSQL dataBase;

static FILE* log;
static FILE* insertTime;
static FILE* mysqlInsertTime;
static FILE* searchTime;
static FILE* mysqlSearchTime;
static FILE* removeTime;
static FILE* mysqlRemoveTime;

static char* server_parser(const char* value);

/**
* \brief Inicialização do servidor.
* 
* Se conecta com o banco de dados e abre os arquivos de log e resultados.
* \param hostName Nome do host onde está o servidor MySQL.
* \param port Porta em que se deve conectar ao MySQL, 0 para o valor padrão.
* \param user Usuário do MySQL.
* \param password Senha do usuário.
* \param data_base Base de dados em que será feita a conexão.
* \return "true" em caso de sucesso e "false" caso contrário. 
*/
bool server_connectDataBase(const char* hostName, unsigned int port,const char* user, const char* password, const char* data_base){
	if(!(mysql_init(&dataBase) && mysql_real_connect(&dataBase,hostName,user,password,data_base,port,NULL,0))){
		fprintf(stderr, "%s, %s, %d: Failed to connect to database. Error: %s\n", __FILE__, __func__, __LINE__, mysql_error(&dataBase));
		return false;
	}
	log = fopen("server_log.out","w");
	insertTime = fopen("server_insert.out","w");
	mysqlInsertTime = fopen("mysql_insert.out","w");
	searchTime = fopen("server_search.out","w");
	mysqlSearchTime = fopen("mysql_search.out","w");
	removeTime = fopen("server_remove.out","w");
	mysqlRemoveTime = fopen("mysql_remove.out","w");
	return true;
}

/**
* \brief Busca de registros.
* 
* Faz a busca no banco de dados por tuplas que casam com os parâmetros passados.
* \param search Dados que serão buscados.
* \param result Referência a um ponteiro em que será atribuído o vetor com os resultados da busca.
* \return Número de tuplas obtidas como resultado.
*/
size_t server_search(Registry search,Registry** result){
	MYSQL_RES *res;
	MYSQL_ROW rows;
	size_t loop;
	my_ulonglong num_rows = 0;
	char* query;
	char* string;
	size_t query_len = 0;
	bool and_flag = false;
	chronometer_t crono;
	chronometer_t crono2;
	*result = NULL;
	chronometerStart(&crono);

	//Montagem da query
	query_len += strlen(QUERY_TEMPLATE_SELECT TABLE QUERY_TEMPLATE_END) + 1;
	query = malloc(query_len);
	strcpy(query,QUERY_TEMPLATE_SELECT TABLE);
	if(search.nome != NULL && search.nome[0] != '\0'){
		string = server_parser(search.nome);
		query_len += strlen(QUERY_TEMPLATE_WHERE) + strlen(QUERY_TEMPLATE_NOME) + strlen(string) + strlen(QUERY_TEMPLATE_SEPARATOR);
		query = realloc(query,query_len);
		strcat(query,QUERY_TEMPLATE_WHERE QUERY_TEMPLATE_NOME);
		strcat(query,string);
		strcat(query,QUERY_TEMPLATE_SEPARATOR);
		and_flag = true;
		free(string);
	}
	if(search.CPF != NULL && search.CPF[0] != '\0'){
		string = server_parser(search.CPF);
		query_len += ((and_flag) ? strlen(QUERY_TEMPLATE_AND) : strlen(QUERY_TEMPLATE_WHERE)) + strlen(QUERY_TEMPLATE_CPF) + strlen(string) + strlen(QUERY_TEMPLATE_SEPARATOR);
		query = realloc(query,query_len);
		(and_flag) ? strcat(query,QUERY_TEMPLATE_AND) : strcat(query,QUERY_TEMPLATE_WHERE);
		strcat(query,QUERY_TEMPLATE_CPF);
		strcat(query,string);
		strcat(query,QUERY_TEMPLATE_SEPARATOR);
		and_flag = true;
		free(string);
	}
	if(search.RG != NULL && search.RG[0] != '\0'){
		string = server_parser(search.RG);
		query_len += ((and_flag) ? strlen(QUERY_TEMPLATE_AND) : strlen(QUERY_TEMPLATE_WHERE)) + strlen(QUERY_TEMPLATE_RG) + strlen(string) + strlen(QUERY_TEMPLATE_SEPARATOR);
		query = realloc(query,query_len);
		(and_flag) ? strcat(query,QUERY_TEMPLATE_AND) : strcat(query,QUERY_TEMPLATE_WHERE);
		strcat(query,QUERY_TEMPLATE_RG);
		strcat(query,string);
		strcat(query,QUERY_TEMPLATE_SEPARATOR);
		and_flag = true;
		free(string);
	}
	if(search.cargo != NULL && search.cargo[0] != '\0'){
		string = server_parser(search.cargo);
		query_len += ((and_flag) ? strlen(QUERY_TEMPLATE_AND) : strlen(QUERY_TEMPLATE_WHERE)) + strlen(QUERY_TEMPLATE_CARGO) + strlen(string) + strlen(QUERY_TEMPLATE_SEPARATOR);
		query = realloc(query,query_len);
		(and_flag) ? strcat(query,QUERY_TEMPLATE_AND) : strcat(query,QUERY_TEMPLATE_WHERE);
		strcat(query,QUERY_TEMPLATE_CARGO);
		strcat(query,string);
		strcat(query,QUERY_TEMPLATE_SEPARATOR);
		and_flag = true;
		free(string);
	}

	strcat(query,QUERY_TEMPLATE_END);

#ifndef NDEBUG
	printf("%s, %s, %d: query: %s\n", __FILE__, __func__, __LINE__, query);
#endif

	chronometerStart(&crono2);
	//Envio da query ao MySQL
	if(mysql_query(&dataBase,query)){
		free(query);
		fprintf(stderr, "%s, %s, %d: Malformed query. Error: %s\n", __FILE__, __func__, __LINE__, mysql_error(&dataBase));
		return num_rows;
	}

	fprintf(log,"%s\n",query);
	free(query);

	//Aquisição dos resultados
	if((res = mysql_store_result(&dataBase))){
		if((num_rows = mysql_num_rows(res)))
			*result = malloc(sizeof(Registry)*num_rows);
			
#ifndef NDEBUG
		printf("%s, %s, %d: Number of rows: %llu\n", __FILE__, __func__, __LINE__, num_rows);
#endif
		for (loop =0; (rows = mysql_fetch_row(res)) != NULL; loop++){
			(*result)[loop].nome = malloc(strlen(rows[0]) + 1);
			strcpy((*result)[loop].nome,rows[0]);
#ifndef NDEBUG
			printf("%s, %s, %d: Nome: %s\n", __FILE__, __func__, __LINE__, (*result)[loop].nome);
#endif

			(*result)[loop].CPF = malloc(strlen(rows[1]) + 1);
			strcpy((*result)[loop].CPF,rows[1]);
#ifndef NDEBUG
			printf("%s, %s, %d: CPF: %s\n", __FILE__, __func__, __LINE__, (*result)[loop].CPF);
#endif

			(*result)[loop].RG = malloc(strlen(rows[2]) + 1);
			strcpy((*result)[loop].RG,rows[2]);
#ifndef NDEBUG
			printf("%s, %s, %d: RG: %s\n", __FILE__, __func__, __LINE__, (*result)[loop].RG);
#endif

			(*result)[loop].cargo = malloc(strlen(rows[3]) + 1);
			strcpy((*result)[loop].cargo,rows[3]);
#ifndef NDEBUG
			printf("%s, %s, %d: Cargo: %s\n", __FILE__, __func__, __LINE__, (*result)[loop].cargo);
#endif

		}
	}
	
	chronometerPause(&crono2);

	mysql_free_result(res);

	chronometerPause(&crono);

	fprintf(searchTime,"%03ld.%09ld\n",chronometerGetSec(crono),chronometerGetNSec(crono));	
	fprintf(mysqlSearchTime,"%03ld.%09ld\n",chronometerGetSec(crono2),chronometerGetNSec(crono2));	

	return num_rows;
}

/**
* \brief Faz a inserção dos dados da estrutura no SGBD.
*
* Recebe o registro a ser guardado no banco de dados. Faz
* a query a ser colocada no banco e envia.
* \param reg Registro a ser inserido.
*/
void server_insert(Registry *reg){
	chronometer_t crono;
	chronometer_t crono2;
	chronometerStart(&crono);
	char query[200];// Query que será enviada ao SGBD

	// Verifica chave primária. Se não for dada sai.
	if(reg->CPF == NULL){
		//nao insere
		return  ;
  	}
  
	char *nome = server_parser(reg->nome);
	char *CPF = server_parser(reg->CPF);
	char *RG = server_parser(reg->RG);
	char *cargo = server_parser(reg->cargo);
  
	sprintf(query, "INSERT INTO "TABLE" (nome,CPF,RG,cargo) VALUES ('%s','%s','%s','%s');", nome, CPF, RG, cargo);
  
	free(nome);
	free(CPF);
	free(RG);
	free(cargo);
	//Envia a query
	
	chronometerStart(&crono2);
	mysql_query(&dataBase, query);
	chronometerPause(&crono2);

	chronometerPause(&crono);

	fprintf(insertTime,"%03ld.%09ld\n",chronometerGetSec(crono),chronometerGetNSec(crono));
	fprintf(mysqlInsertTime,"%03ld.%09ld\n",chronometerGetSec(crono2),chronometerGetNSec(crono2));

	fprintf(log,"%s\n",query);
	return;
}

/**
* \brief Faz a remoção de uma tupla no banco a partir de um CPF.
* \param reg Registro a ser removido.
*/
void server_remove(Registry *reg){
	chronometer_t crono;
	chronometer_t crono2;
	chronometerStart(&crono);
	char query[200];// Query que será enviada ao SGBD

	if(reg->CPF == NULL){
		return;

	}
   
	char *CPF = server_parser(reg->CPF);
	sprintf(query,"DELETE FROM "TABLE" WHERE CPF=%s ;", CPF);
  
	free(CPF);

	//Envia a query
        chronometerStart(&crono2);
	mysql_query(&dataBase, query);
	chronometerPause(&crono2);

	chronometerPause(&crono);

	fprintf(removeTime,"%03ld.%09ld\n",chronometerGetSec(crono),chronometerGetNSec(crono));
	fprintf(mysqlRemoveTime,"%03ld.%09ld\n",chronometerGetSec(crono2),chronometerGetNSec(crono2));

	fprintf(log,"%s\n",query);
	return;
}

/**
* \brief Finalização do servidor.
* 
* Fecha a conexão com o MySQL e fecha os arquivos de log e resultados.
*/
void server_closeConnection(){
	fclose(log);
	fclose(insertTime);
	fclose(mysqlInsertTime);
	fclose(searchTime);
	fclose(mysqlSearchTime);
	fclose(removeTime);
	fclose(mysqlRemoveTime);
	mysql_close(&dataBase);
}

/**
* \brief Tratamento de strings da query.
*
* Duplica as aspas simples ('), evitando que a query seja confundida.
*/
static char* server_parser(const char* value){
	size_t loop;
	char* string;
	size_t string_size;
	string_size = strlen(value) + 1;
	string = malloc(string_size);
	strcpy(string,value);
#ifndef NDEBUG
	printf("%s, %s, %d: Input: %s\n", __FILE__, __func__, __LINE__, value);
#endif
	for(loop = 0; loop < string_size; loop++){
		if(string[loop] == '\''){
			string = realloc(string,string_size + 1);
			memmove(string+loop+1,string+loop,string_size - loop);
			string_size++;
			string[loop] = '\'';
			loop++;
		}
	}
#ifndef NDEBUG
	printf("%s, %s, %d: Output: %s\n", __FILE__, __func__, __LINE__, string);
#endif
	return string;
}

